[Mybatis]Mybatis基础.

介绍

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。

  • 每个基于MyBatis的应用都是以一个 SqlSessionFactory 的实例为核心的。
  • 我们可以通过SqlSessionFactory来创建多个新的会话,SqlSession对象。
  • 每个会话就相当于我不同的地方登陆一个账号去访问数据库。
  • 而通过SqlSession就可以完成几乎所有的数据库操作,我们发现这个接口中定义了大量数据库操作的方法,因此,现在我们只需要通过一个对象就能完成数据库交互了,极大简化了之前的流程。

动态SQL

根据用户输入的不同参数的组合来生成具有不同查询调价的SQL语句。

缓存机制

我们可以将一部分内容放入缓存,下次需要获取数据时,我们就可以直接从缓存中读取,这样相当于直接从缓存中获取,而不是再向数据库进行请求。

  • Mybatis存在一级缓存和二级缓存。

    一级缓存

  • 是MyBatis默认开启的缓存机制。
  • 指的是SqlSession级别的缓存,即在同一个SqlSeesion中执行的相同的SQL语句的查询结果会被缓存起来,下一次执行同样的查询时,直接从缓存中获取结果,而不需呀再次向数据库发送SQL查询语句。
  • 一级缓存只在SqlSession的生命周期内有效,当SqlSession关闭后,缓存数据也会被清空。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static void main(String[] args) throws InterruptedException {
    try (SqlSession sqlSession = MybatisUtil.getSession(true)){
    TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
    Student student1 = testMapper.getStudentBySid(1);
    Student student2 = testMapper.getStudentBySid(1);
    System.out.println(student1 == student2);
    }
    }
    out:
    true
  • 我们发现,两次得到的是同一个Student对象,也就是说我们第二次查询并没有重新去构造对象,而是直接得到之前创建好的对象。构造方法只会被调用一次。
  • 因为先从一级缓存中取数据,如果有就不会访问数据库了。

  • 如果我们修改了数据库中的内容,缓存还会生效吗:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) throws InterruptedException {
    try (SqlSession sqlSession = MybatisUtil.getSession(true)){
    TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
    Student student1 = testMapper.getStudentBySid(1);//第一次构造
    testMapper.addStudent(new Student().setName("小李").setSex("男"));//第二次 new了一个
    Student student2 = testMapper.getStudentBySid(1);//第三次构造
    System.out.println(student1 == student2);
    }
    }
    out:
    fasel
  • 被构造了三次。当我们进行了插入操作后,缓存就没有生效了,我们再次进行查询得到的是一个新创建的对象。
  • 一级缓存,在进行DML操作后,会使得缓存失效。
  • 也就是说Mybatis知道我们对数据库里面的数据进行了修改,所以之前缓存的内容可能就不是当前数据库里面最新的内容了。
  • 还有一种情况就是,当前会话结束后,也会清理全部的缓存,因为已经不会再用到了。但是一定注意,一级缓存只针对于单个会话,多个会话之间不相通。
    一个会话DML操作只会重置当前会话的缓存,不会重置其他会话的缓存,也就是说,其他会话缓存是不会更新的!
  • 会导致缓存数据不一致的问题,第一个会话中修改了数据,但第二个会话不知道,依旧从缓存中取数据。得到的数据就会不一致。

二级缓存

  • 二级缓存是MyBatis的全局缓存机制。
  • 他可以缓存多个SqlSeesion共享的数据,即多个SqlSession共享一个二级缓存。
  • 是namespace级别的,每个Mapper的namespace都拥有自己的一个二级缓存。
  • 开启了之后,每次执行查询操作时,MyBatis会先从二级缓存中获取数据,如果没有则会去一级缓存,如果一级缓存没有则会去数据库查询数据并将查询结果存入一级、二级缓存中。
  • 二级缓存的数据更新并不是实时的,当数据库中的数据发生变化时,二级缓存中的数据并不会自动更新,需要进行缓存的清除和刷新。
  • 读取顺序:二级缓存 ≥ 一级缓存 ≥ 数据库

如果我们不使用MyBatis修改数据,从外部修改,MyBatis的缓存依旧在生效,得到的数据仍然是修改前的数据,如果要解决问题,只能关闭MyBatis的缓存来保证一致性

数据源

  • 数据库链接的建和关闭都是极其耗费系统资源的操作。
  • 通过DriveManager获取的数据库连接,一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完后立即关闭连接,频繁的打开、关闭连接会持续消耗网络资源,造成整个系统性能的低下。
  • 因此JDBC定义了一个数据源的标准,也就是DataSource接口,告诉数据源数据库的连接信息,并将所有的连接全部交给数据源进行集中管理。
  • 当需要一个Connection对象时,可以向数据源申请,数据源会根据内部机制,合理地分配连接对象给我们。
  • 一般的DataSource实现,都是采用的池化技术,就是在一开始就创建好N个连接,这样之后使用就无需再次进行连接,而是直接使用现成的Connection对象进行数据库操作。

MyBatis事务管理

事务遵循一个ACID原则:

  • 原子性:事务是一个院子操作,由一系列组成,要么全部完成,要么完全不起作用。
  • 一致性:一旦事务完成(不管成功还是失败),系统必须确保它所建模的事务处于一致的状态,而不是部分完成部分不完成。在现实中的数据也不应该被破坏。
  • 隔离性:可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据破坏。
  • 持久性:一旦事务完成,无论发生什么系统错误,它的结果都不应该收影响,这样能从任何系统崩溃中恢复出来。通常情况下,事务的结果被写到持久化存储器中。

简单的说就是事务要么全部完成,要么啥都不做,并且不同的事务直接相互隔离,互不干扰

隔离级别:

  • 读未提交:其他事务会读取到当前事务未提交的内容,存在脏读问题。
  • 读已提交:只能读取到其他事务已经提交的内容,存在不可重复读问题。
  • 可重复读:在读取某行数据后,其他事务不允许操作此行,直到事务结束。(仅仅是不允许修改)
  • 串行:一个事务必须等待其他事务结束后才可以开始执行。

三种情况:

  • 脏读:读取到了被回滚的数据。
  • 虚读(不可重复读):由于其他事务更新数据,两次读取的数据不一样。
  • 幻读:由于其他食物执行插入删除操作,而又无法感知表中记录条数发生变化,当再次再读取时会莫名其妙多出货缺失数据,就像产生幻觉一样。

  • Mybatis对于数据库的事务管理,也有着相应的封装。一个事务无非就是创建、提交、回滚、关闭,因此这些操作被Mybatis抽象为一个接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public interface Transaction {
    Connection getConnection() throws SQLException;

    void commit() throws SQLException;

    void rollback() throws SQLException;

    void close() throws SQLException;

    Integer getTimeout() throws SQLException;
    }

Springboot配置Mybatis

  • 在.yaml文件中配置数据源即可
    1
    2
    3
    4
    5
    6
    spring:
    datasource:
    url: jdbc:mysql://localhost:3306/study
    username: root
    password: saier100le
    driver-class-name: com.mysql.cj.jdbc.Driver

页偏移过大怎么解决

  • 使用PageHelper
  • 调用PageHelper.startPage(pageNo,pageLimit)的时候,就已经静悄悄地把我们的分页参数存储到一个变量 ThreadLocal LOCAL_PAGE;
  • 执行userMapper进行查询,实际上是被一个叫做PageInterceptor.java拦截到了,执行了它重写的interceptor方法。